feat: cache provider slash commands on project creation and session start#1583
feat: cache provider slash commands on project creation and session start#1583deyvid-bardarov wants to merge 5 commits intopingdotgg:mainfrom
Conversation
…tart Discover provider slash commands eagerly on project creation via a lightweight Claude SDK probe query, and update the cache when a session reports its commands via session.configured. Commands are persisted server-side in the project projection (SQLite) and served to the web client through the orchestration snapshot, enabling a 3-tier fallback: session commands → project-level cache → localStorage cache.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
Outdated
Show resolved
Hide resolved
| return names; | ||
| } | ||
| return providerSlashCommandsCache.get(selectedProvider) ?? EMPTY_PROVIDER_SLASH_COMMANDS; | ||
| }, [sessionSlashCommands, projectCachedCommands, selectedProvider]); |
There was a problem hiding this comment.
Side effects inside useMemo violate React purity
Low Severity
The useMemo callback mutates a module-level Map (providerSlashCommandsCache) and writes to localStorage via persistProviderSlashCommandsCache. React requires useMemo callbacks to be pure. In Strict Mode, React may invoke this callback twice per render, causing redundant localStorage writes. A useEffect is the appropriate place for these side effects.
The slash command extraction from session.configured events was unreachable because it was nested inside the assistantDelta check, which is only true for content.delta events.
Read claudeSettings.binaryPath from serverSettingsService instead of hardcoding "claude", matching startSession behavior.
…worker Prevents a malformed project.created payload from killing the entire stream with an uncaught defect. Decode errors are now logged and the worker continues processing subsequent events.
Lowercase the command name before startsWith comparison so mixed-case provider commands like "CustomCmd" match when the user types /customcmd.
| } as SDKUserMessage; | ||
| } | ||
|
|
||
| return yield* Effect.tryPromise({ |
There was a problem hiding this comment.
🟡 Medium Layers/ClaudeAdapter.ts:3058
When probeQuery.supportedCommands() hangs past the 15-second timeout, probeQuery.close() is never called and the resource leaks. The Effect.timeout fails the Effect with TimeoutError, but the underlying Promise keeps running — the finally block at line 3079 only runs when the Promise settles, not when the timeout fires. Consider restructuring to ensure close() is always invoked, for example by using Effect.acquireUseRelease or Effect.onInterrupt to trigger cleanup when the Effect is interrupted.
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/ClaudeAdapter.ts around line 3058:
When `probeQuery.supportedCommands()` hangs past the 15-second timeout, `probeQuery.close()` is never called and the resource leaks. The `Effect.timeout` fails the Effect with `TimeoutError`, but the underlying Promise keeps running — the `finally` block at line 3079 only runs when the Promise settles, not when the timeout fires. Consider restructuring to ensure `close()` is always invoked, for example by using `Effect.acquireUseRelease` or `Effect.onInterrupt` to trigger cleanup when the Effect is interrupted.
Evidence trail:
apps/server/src/provider/Layers/ClaudeAdapter.ts lines 3058-3092 (REVIEWED_COMMIT): Shows `Effect.tryPromise` with inner `try/finally` block where `probeQuery.close()` is in the `finally`, wrapped by `Effect.timeout(Duration.seconds(15))`. The `finally` block at line ~3079 only runs when the Promise settles, not when Effect.timeout fires the interruption.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| import Migration0017 from "./Migrations/017_ProjectionThreadsArchivedAt.ts"; | ||
| import Migration0018 from "./Migrations/018_ProjectionThreadsArchivedAtIndex.ts"; | ||
| import Migration0019 from "./Migrations/016_ProjectionThreadSessionSlashCommands.ts"; | ||
| import Migration0020 from "./Migrations/017_ProjectionProjectSlashCommandsCache.ts"; |
There was a problem hiding this comment.
Migration files named with wrong numeric prefix
Low Severity
The new migration files are named 016_ProjectionThreadSessionSlashCommands.ts and 017_ProjectionProjectSlashCommandsCache.ts, but they are registered as migration IDs 19 and 20. Files 016_CanonicalizeModelSelections.ts and 017_ProjectionThreadsArchivedAt.ts already exist with the same numeric prefixes. This duplication in naming conventions makes it easy for a future contributor to misidentify migration ordering or accidentally reuse an ID.
|
|
||
| export type ComposerTriggerKind = "path" | "slash-command" | "slash-model"; | ||
| export type ComposerSlashCommand = "model" | "plan" | "default"; | ||
| export type ComposerProviderSlashCommand = string; |
There was a problem hiding this comment.


Discover provider slash commands eagerly on project creation via a lightweight Claude SDK probe query, and update the cache when a session reports its commands via session.configured. Commands are persisted server-side in the project projection (SQLite) and served to the web client through the orchestration snapshot, enabling a 3-tier fallback: session commands → project-level cache → localStorage cache.
What Changed
discoverSlashCommandsmethod to the provider adapter interface. On project creation,ProviderCommandReactorlistens forproject.createdevents and fires a lightweight Claude SDK probe query (permissionMode: "plan", immediately closed) to callsupportedCommands(), retrieving rich command metadata (name, description, argumentHint).ProviderSlashCommandInfoschema in contracts and aproject.provider-slash-commands.setcommand/event pair in the event-sourcing pipeline to persist discovered commands on the project aggregate.providerSlashCommandson thread sessions, and one forcached_provider_slash_commands_jsonon the projects projection table.session.configured,ProviderRuntimeIngestionnow extracts slash command names from the init message and updates both the thread session and the project-level cache.Why
By discovering commands eagerly on project creation (in a background fiber that never blocks), the menu is populated immediately. The project-level cache also survives across sessions and browser refreshes, so commands are always available without waiting for a session round-trip.
UI Changes
N/A — No visual changes. The command menu items now appear sooner (before session start) and show richer descriptions when available.
Screenshots
Checklist
Note
Medium Risk
Adds new event-sourced command/event plus SQLite migrations and projection changes to persist slash-command data, which could impact read model hydration and DB compatibility. Runtime ingestion and project-creation background discovery introduce new asynchronous flows that need validation across providers.
Overview
Adds provider slash-command caching end-to-end. Introduces
discoverSlashCommandson provider adapters/services (implemented via a timed Claude probe; Codex/test adapters stubbed) and a newproject.provider-slash-commands.setcommand/event to persist discovered commands on the project aggregate.Persists and hydrates cached commands. Updates projections/snapshot query and adds SQLite migrations to store (1)
cached_provider_slash_commands_jsononprojection_projectsand (2)provider_slash_commands_jsononprojection_thread_sessions, wiring these fields through the projection repositories and snapshot decoding.Populates cache from runtime + surfaces in UI.
ProviderCommandReactornow listens forproject.createdand asynchronously discovers/caches commands, whileProviderRuntimeIngestionextractsslash_commandsfromsession.configuredto update both the thread session and project cache. The web composer consumes session → project cache → localStorage fallback, expands trigger detection to include provider commands, and renders provider slash commands in the menu (with provider icons/descriptions when available).Written by Cursor Bugbot for commit 37aa10f. This will update automatically on new commits. Configure here.
Note
Cache provider slash commands on project creation and session start
discoverSlashCommands()to the Claude and Codex provider adapters; Claude probes the runtime with a 15-second timeout and returns an empty list on failure.project.created,ProviderCommandReactorcallsdiscoverSlashCommandsfor each supported provider and dispatchesproject.provider-slash-commands.setto cache results at the project level.session.configured,ProviderRuntimeIngestionextracts slash commands from the session config and persists them to both the thread session and the project cache.ChatViewreads cached commands from the session or project state (falling back to alocalStoragecache) and surfaces them in the composer slash menu with provider icons and descriptions.provider_slash_commands_jsontoprojection_thread_sessionsandcached_provider_slash_commands_jsontoprojection_projects.Macroscope summarized 37aa10f.